/**
  ETFRoot project, Anyang Normal University && IMP-CAS
  \class ETFDetectorManager
  \brief To manager the creation and registration of detectors according to the users'
  intention. It also by-the-way do the channel-mapping work.
  \author SUN Yazhou, asia.rabbit@163.com
  \since 2021-10-26
  \date 2021-12-03 last modified
  \attention
  changelog
  <table>
  <tr>  <th>Date         <th>Author      <th>Description                   </tr>
  <tr>  <td>2021-10-26   <td>Asia Sun    <td>file created                  </tr>
  </table>

  \copyright Copyright (c) 2021 Anyang Normal U. && IMP-CAS with LGPLv3 LICENSE
*/

#include <iostream>
#include <unistd.h>
#include <regex>
#include <TGraph.h>
#include "FairRootManager.h"
#include "ETFDetectorManager.h"
#include "ETFParaManager.h"
#include "ETFYaml.h"
#include "ETFHPTDCChannel.h"
#include "ETFAmplitudeChannel.h"
#include "ETFMsg.h"
#include "ETFPDCL.h"
#include "ETFPDCR.h"
#include "ETFDC.h"
#include "ETFDCTaL.h"
#include "ETFTstop4.h"
#include "ETFTOFWall.h"
#include "ETFTstop.h"
#include "ETFTstart.h"

using std::cout;
using std::endl;
using std::regex;

ClassImp(ETFDetectorManager);

ETFDetectorManager *ETFDetectorManager::fInstance = nullptr;

ETFDetectorManager::ETFDetectorManager() : fTargetZ(-9999.), fDebug(false){}

ETFDetectorManager *ETFDetectorManager::Instance(){
  if(!fInstance) fInstance = new ETFDetectorManager();
  return fInstance;
} // end member function Instance

ETFDetectorManager::~ETFDetectorManager(){}

// Record det in map fDet and fDetName
void ETFDetectorManager::Record(ETFDetector *det, const char *name){
  fDetName[fDet.size()] = name;
  fDet[name] = det;
  det->SetName(name);
} // end member function Record

// register all dets to main tree
void ETFDetectorManager::Register(){
  // branchname, foldername, array address, persistent or transient
  for(auto &d : fDet) d.second->Register();
} // end member function Register

// create the detectors w.r.t. config file
void ETFDetectorManager::Configure(){
  ETFParaManager *pm = ETFParaManager::Instance();
  ETFYaml chIdConfig = *pm->GetChIdPara();
  ETFYaml detPosConfig = *pm->GetDetPosPara();
  for(const char *t : {"dc0", "dc1", "dc2", "tofw", "tstop",
    "tstart", "pdcU0", "pdcU1", "pdcD0", "pdcD1", "tstop0", "tstop1",
    // for general 4-DC pile-up cosmic-ray tests, as usually done by P. Ma //
    "dcTaL0", "dcTaL1", "dcTaL2", "dcTaL3", "tstop4U", "tstop4D"
    /////////////////////////////////////////////////////////////////////////
  }){
    if(chIdConfig[t] && detPosConfig[t]){
      int id; // dcid, a temporary variable
      if(regex_match(t, regex("^dc\\d{1}$"))) Record(new ETFDC(t), t);
      if(regex_match(t, regex("^pdcU\\d{1}$"))) Record(new ETFPDCL(t), t);
      if(regex_match(t, regex("^pdcD\\d{1}$"))) Record(new ETFPDCR(t), t);
      if(regex_match(t, regex("^p{0,1}dc[UD]{0,1}\\d{1}$")))
        dynamic_cast<ETFVDC *>(fDet[t])->SetDCId(atoi(t+strlen(t)-1));
      if(!strcmp(t, "tofw")) Record(new ETFTOFWall(t), t);
      if(!strcmp(t, "tstart")) Record(new ETFTstart(t), t);
      if(regex_match(t, regex("^tstop[01]{0,1}$"))) Record(new ETFTstop(t), t);
      // for general 4-DC pile-up cosmic-ray tests, as usually done by P. Ma //
      if(regex_match(t, regex("^dcTaL\\d{1}$"))) Record(new ETFDCTaL(t), t);
      if(regex_match(t, regex("^dcTaL\\d{1}$")))
        dynamic_cast<ETFVDC *>(fDet[t])->SetDCId(atoi(t+strlen(t)-1));
      if(regex_match(t, regex("^tstop4[UD]{1}$"))) Record(new ETFTstop4(t), t);
      /////////////////////////////////////////////////////////////////////////
    } // end outer if
    // else ETFMsg::Error("ETFDetectorManager", "Configure: det %s chid or detPos not assigned", t);
  } // end for over the DCs

  if(detPosConfig["target"])
    fTargetZ = detPosConfig["target"]["pos"][2].as<double>();
} // end member function Configure

ETFDetector *ETFDetectorManager::GetDetector(const char *name) const{
  if(!fDet.count(name))
    ETFMsg::Error("ETFDetectorManager", "GetDetector: detector %s not found", name);
  return fDet.at(name);
} // end member function GetDetector

ETFDetector *ETFDetectorManager::GetDetector(int detId) const{
  if(!fDetName.count(detId))
    ETFMsg::Error("ETFDetectorManager", "GetDetector: detector id %d not found", detId);
  return GetDetector(fDetName.at(detId));
} // end member function GetDetector

unsigned ETFDetectorManager::GetBunchID() const{
  return fBunchID;
} // end member function GetBunchID

// identify ch as a hit and add to a specific detector
void ETFDetectorManager::Assign(ETFHPTDCChannel *ch){
  if(!ch->GetNofLeadingEdges()) return; // no leading edges, invalid channel
  const int chid = ch->GetChannelId();
  if(!fChMap.count(chid)) return; // a homeless channel
  const unsigned uid = fChMap.at(chid);
  if(-2 == uid) return; // dummy uid => the channel is a dummy channel
  if(ch->IsPXI()) ch->SubtractTrigger(GetBunchID()); // set time start to trigger time
  ETFDetector *det = GetDetector(uid & 0xFF);
  if(!det->TrigBoundCheck(ch)) return; // not close enough to trig, not from trig-generator particle
  det->Assign(ch);
  if(fDebug){ ch->Print(); getchar(); }
} // end member function Assign(T)

void ETFDetectorManager::Assign(const ETFAmplitudeChannel *ch){
  const int chid = ch->GetChannelId();
  if(!fChMap.count(chid)) return; // a homeless channel
  const unsigned uid = fChMap.at(chid);
  if(-2 == uid) return; // dummy uid => the channel is a dummy channel
  GetDetector(uid & 0xFF)->Assign(ch);
} // end member function Assign(A)

unsigned ETFDetectorManager::GetUID(int chid) const{
  if(!fChMap.count(chid)) ETFMsg::Error("ETFDetectorManager", "GetUID: no record for chid %d", chid);
  return fChMap.at(chid);
} // end member function GetUID

void ETFDetectorManager::Reset(){
  fBunchID = 0;
  fEventIndex = -1;
  for(auto &d : fDet) d.second->Reset();
} // end member function Reset

void ETFDetectorManager::Draw(){
  ConfigureDraw();
  fGMagnet->Draw("plsame");
  fGBeamLine->Draw("plsame");
  fGTarget->Draw("plsame");
  for(auto &p : fDet) p.second->Draw();
} // end member function Draw

// draw the dipole magnet and the neutral beamline
void ETFDetectorManager::ConfigureDraw(){
  // show the magnet
  fGMagnet = new TGraph();
	fGMagnet->SetMarkerColor(5); fGMagnet->SetMarkerStyle(29);
	fGMagnet->SetLineWidth(3); fGMagnet->SetLineColor(3);
	fGMagnet->SetPoint(0, 475., 500.); fGMagnet->SetPoint(1, 475., -500.);
	fGMagnet->SetPoint(2, -475., -500.); fGMagnet->SetPoint(3, -475., 500.);
	fGMagnet->SetPoint(4, 475., 500.);
  // show the beamline
  fGBeamLine = new TGraph();
	fGBeamLine->SetLineColor(8); fGBeamLine->SetLineStyle(3); fGBeamLine->SetLineWidth(2);
	fGBeamLine->SetPoint(0, -10000., 0.); fGBeamLine->SetPoint(1, 15000., 0.);
  // show the target
  fGTarget = new TGraph();
  fGTarget->SetMarkerStyle(29); fGTarget->SetMarkerColor(kRed);
  fGTarget->SetMarkerSize(2);
  fGTarget->SetPoint(0, fTargetZ, 0.);
} // end member function ConfigureDraw

void ETFDetectorManager::Print(){
  cout << "\033[33;1m########## DETECTOR MANAGER PRINT ##############\033[0m" << endl;
  for(auto &d : fDet) d.second->Print();
} // end member function Reset
